/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.navigator;

import static com.android.tools.idea.testing.TestProjectPaths.SIMPLE_APPLICATION;
import static com.android.tools.idea.testing.flags.FlagUtils.overrideForTest;
import static com.google.common.truth.Truth.assertThat;
import static com.intellij.openapi.util.io.FileUtil.join;
import static com.intellij.openapi.util.io.FileUtil.writeToFile;
import static org.jetbrains.android.AndroidTestBase.refreshProjectFiles;
import static com.android.tools.idea.navigator.AndroidProjectViewPane.PROJECT_VIEW_DEFAULT_KEY;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.android.testutils.SystemPropertyOverrides;
import com.android.testutils.VirtualTimeScheduler;
import com.android.tools.analytics.LoggedUsage;
import com.android.tools.analytics.TestUsageTracker;
import com.android.tools.analytics.UsageTracker;
import com.android.tools.idea.IdeInfo;
import com.android.tools.idea.flags.StudioFlags;
import com.android.tools.idea.gradle.project.model.GradleAndroidModel;
import com.android.tools.idea.gradle.projectView.AndroidProjectViewSettingsImpl;
import com.android.tools.idea.navigator.nodes.AndroidViewProjectNode;
import com.android.tools.idea.navigator.nodes.android.BuildScriptTreeStructureProvider;
import com.android.tools.idea.projectsystem.AndroidProjectSystem;
import com.android.tools.idea.project.AndroidNotification;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
import com.android.tools.idea.projectsystem.gradle.GradleProjectSystem;
import com.android.tools.idea.testing.AndroidGradleProjectRule;
import com.android.tools.idea.testing.AndroidGradleTests;
import com.android.tools.idea.testing.IdeComponents;
import com.android.tools.idea.testing.TestModuleUtil;
import com.android.tools.idea.util.CommonAndroidUtil;
import com.android.utils.FileUtils;
import com.google.common.io.FileWriteMode;
import com.google.common.io.Files;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.google.wireless.android.sdk.stats.ProjectViewDefaultViewEvent;
import com.intellij.ide.projectView.ProjectViewSettings;
import com.intellij.ide.projectView.TreeStructureProvider;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
import com.intellij.ide.projectView.impl.ProjectViewState;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.projectView.TestProjectTreeStructure;
import com.intellij.testFramework.DisposableRule;
import com.intellij.testFramework.EdtRule;
import com.intellij.testFramework.RunsInEdt;
import com.intellij.util.containers.ContainerUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.rules.TemporaryFolder;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

// TODO: Test available actions for each node!
@RunsInEdt
public class AndroidProjectViewTest {
  AndroidGradleProjectRule projectRule = new AndroidGradleProjectRule();
  TemporaryFolder tempFolder = new TemporaryFolder();
  DisposableRule disposableRule = new DisposableRule();
  @Rule
  public TestRule rule = RuleChain.outerRule(projectRule).around(new EdtRule()).around(disposableRule).around(tempFolder);
  private AndroidProjectViewPane myPane;

  @Test
  public void testGeneratedSourceFiles_lightClasses() throws Exception {
    projectRule.loadProject(SIMPLE_APPLICATION);

    // Create BuildConfig.java in one of the generated source folders.
    Module appModule = TestModuleUtil.findAppModule(projectRule.getProject());
    GradleAndroidModel androidModel = GradleAndroidModel.get(appModule);
    assertThat(androidModel).isNotNull();
    Collection<File> generatedFolders = androidModel.getMainArtifact().getGeneratedSourceFolders();
    assertThat(generatedFolders).isNotEmpty();

    File buildConfigFolder = generatedFolders.stream().filter(f -> f.getPath().contains("buildConfig")).findFirst().orElse(null);
    assertThat(buildConfigFolder).isNotNull();
    writeToFile(new File(buildConfigFolder, join("com", "application", "BuildConfig.java")),
                "package com.application; public final class BuildConfig {}");

    refreshProjectFiles();
    AndroidGradleTests.waitForSourceFolderManagerToProcessUpdates(projectRule.getProject());
    myPane = createPane();
    TestAndroidTreeStructure structure = new TestAndroidTreeStructure(projectRule.getProject(), projectRule.getFixture().getTestRootDisposable());

    Set<List<String>> allNodes = getAllNodes(structure);
    assertThat(allNodes).contains(Arrays.asList("app (Android)", "java (generated)", "application", "BuildConfig"));
  }

  @Test
  public void testGeneratedResources() throws Exception {
    Function1<File, Unit> patch = (projectRoot) -> {
      try {
        Files.asCharSink(new File(projectRoot, "app/build.gradle"), StandardCharsets.UTF_8, FileWriteMode.APPEND)
            .write(
              """

                android {
                  String resGeneratePath = "${buildDir}/generated/my_generated_resources/res"
                    def generateResTask = tasks.create(name: 'generateMyResources').doLast {
                        def rawDir = "${resGeneratePath}/raw"
                        mkdir(rawDir)
                        file("${rawDir}/sample_raw_resource").write("sample text")
                    }
                    def resDir = files(resGeneratePath).builtBy(generateResTask)
                    applicationVariants.all { variant ->
                        variant.registerGeneratedResFolders(resDir)
                    }
                }""");
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
      return Unit.INSTANCE;
    };
    // TODO add noSync variant in case of test failures
    projectRule.loadProject(SIMPLE_APPLICATION, null, null, patch);

    Module appModule = TestModuleUtil.findAppModule(projectRule.getProject());
    GradleAndroidModel androidModel = GradleAndroidModel.get(appModule);
    File generatedResourcesFolder = androidModel.getMainArtifact()
      .getGeneratedResourceFolders()
      .stream()
      .filter(f -> f.getPath().contains("my_generated_resources"))
      .findFirst()
      .orElse(null);
    assertThat(generatedResourcesFolder).named("my_generated_resources folder").isNotNull();
    File resourceFile = FileUtils.join(generatedResourcesFolder, "raw", "sample_raw_resource");
    Files.createParentDirs(resourceFile);
    Files.write("\nsample text", resourceFile, StandardCharsets.UTF_8);

    refreshProjectFiles();

    myPane = createPane();
    TestAndroidTreeStructure structure = new TestAndroidTreeStructure(projectRule.getProject(), projectRule.getFixture().getTestRootDisposable());

    Set<List<String>> allNodes = getAllNodes(structure);
    assertThat(allNodes).contains(Arrays.asList("app (Android)", "res (generated)", "raw", "sample_raw_resource"));
  }

  @Test
  public void testGeneratedAssets() throws Exception {
    Function1<File, Unit> patch = (projectRoot) -> {
      try {
        Files.asCharSink(new File(projectRoot, "app/build.gradle"), StandardCharsets.UTF_8, FileWriteMode.APPEND)
          .write(
            """

             abstract class AssetGenerator extends DefaultTask {
                 @OutputDirectory
                 abstract DirectoryProperty getOutputDirectory();
                 @TaskAction
                 void run() {
                     def outputFile = new File(getOutputDirectory().get().getAsFile(), "foo.txt")
                     new FileWriter(outputFile).with {
                         write("some text")
                         flush()
                     }
                 }
             }
             def writeAssetTask = tasks.register("createAssets", AssetGenerator.class)
             androidComponents {
                 onVariants(selector().all(),  { variant ->
                     variant.sources.assets.addGeneratedSourceDirectory(writeAssetTask, AssetGenerator::getOutputDirectory)
                 })
             }""");
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
      return Unit.INSTANCE;
    };
    // TODO add noSync variant in case of test failures
    projectRule.loadProject(SIMPLE_APPLICATION, null, null, patch);

    Module appModule = TestModuleUtil.findAppModule(projectRule.getProject());
    GradleAndroidModel androidModel = GradleAndroidModel.get(appModule);
    File generatedAssetsFolder = androidModel.getMainArtifact()
      .getGeneratedAssetFolders()
      .stream()
      .filter(f -> f.getPath().contains("createAssets"))
      .findFirst()
      .orElse(null);
    assertThat(generatedAssetsFolder).named("createAssets folder").isNotNull();

    File assetFile = FileUtils.join(generatedAssetsFolder, "raw", "createAssets");
    Files.createParentDirs(assetFile);
    Files.asCharSink(assetFile, StandardCharsets.UTF_8).write("\nsample text");

    refreshProjectFiles();

    myPane = createPane();
    TestAndroidTreeStructure structure = new TestAndroidTreeStructure(projectRule.getProject(), projectRule.getFixture().getTestRootDisposable());

    Set<List<String>> allNodes = getAllNodes(structure);
    assertThat(allNodes).contains(Arrays.asList("app (Android)", "assets (generated)", "raw", "createAssets"));
  }

  @Test
  public void testShowVisibilityIconsWhenOptionIsSelected() {
    ProjectViewState projectViewState = projectRule.getProject().getService(ProjectViewState.class);
    projectViewState.setShowVisibilityIcons(true);

    myPane = createPane();
    ProjectAbstractTreeStructureBase structure = myPane.createStructure();
    assertThat(((ProjectViewSettings)structure).isShowVisibilityIcons()).isTrue();
  }

  @Test
  public void testShowVisibilityIconsWhenOptionIsUnselected() {
    ProjectViewState projectViewState = projectRule.getProject().getService(ProjectViewState.class);
    projectViewState.setShowVisibilityIcons(false);

    myPane = createPane();
    ProjectAbstractTreeStructureBase structure = myPane.createStructure();
    assertThat(((ProjectViewSettings)structure).isShowVisibilityIcons()).isFalse();
  }

  @Test
  public void testResourcesPropertiesInAndroidView() throws Exception {
    projectRule.loadProject(SIMPLE_APPLICATION);
    FileUtils.createFile(new File(projectRule.getProject().getBasePath(), "/app/src/main/res/resources.properties"), "");

    refreshProjectFiles();
    AndroidGradleTests.waitForSourceFolderManagerToProcessUpdates(projectRule.getProject());
    myPane = createPane();
    TestAndroidTreeStructure structure = new TestAndroidTreeStructure(projectRule.getProject(), projectRule.getFixture().getTestRootDisposable());

    Set<List<String>> allNodes = getAllNodes(structure);
    assertThat(allNodes).contains(Arrays.asList("app (Android)", "res", "resources.properties (main)"));
  }

  @Test
  public void testAndroidViewIsDefault() throws Exception {
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    Project project = projectRule.getProject();
    GradleProjectSystem mockGradleProjectSystem = Mockito.mock(GradleProjectSystem.class);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockGradleProjectSystem);
    when(mockGradleProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);
    myPane = createPane();

    assertThat(myPane.isInitiallyVisible()).isTrue();

    overrideForTest(StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS, false, disposableRule.getDisposable());
    assertThat(settings.isDefaultToProjectViewVisible()).isFalse();

    overrideForTest(StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS, true, disposableRule.getDisposable());
    assertThat(settings.isDefaultToProjectViewVisible()).isTrue();
    when(ideInfo.isAndroidStudio()).thenReturn(false);
    when(ideInfo.isGameTools()).thenReturn(false);
    assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault").isFalse();

    when(ideInfo.isAndroidStudio()).thenReturn(true);
    when(ideInfo.isGameTools()).thenReturn(false);
    assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isTrue();

    when(ideInfo.isAndroidStudio()).thenReturn(false);
    when(ideInfo.isGameTools()).thenReturn(true);
    assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(GameTools)").isTrue();

    try (SystemPropertyOverrides override = new SystemPropertyOverrides()) {
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "true");
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      when(ideInfo.isAndroidStudio()).thenReturn(false);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(property)").isFalse();

      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio, property)").isFalse();

      when(ideInfo.isAndroidStudio()).thenReturn(false);
      when(ideInfo.isGameTools()).thenReturn(true);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(GameTools, property)").isFalse();

      settings.setDefaultToProjectView(true);
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "false");
      StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS.override(true);
    try {
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      when(ideInfo.isAndroidStudio()).thenReturn(false);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(settings)").isFalse();

      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio, settings)").isFalse();

      when(ideInfo.isAndroidStudio()).thenReturn(false);
      when(ideInfo.isGameTools()).thenReturn(true);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(GameTools, settings)").isFalse();
    } finally {
      StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS.clearOverride();
    }
  }

    commonAndroidUtilMockedStatic.close();
    projectSystemUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewNotVisibleInUnsupportedProjectSystem() {
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    Project project = projectRule.getProject();
    AndroidProjectSystem mockProjectSystem = Mockito.mock(AndroidProjectSystem.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockProjectSystem);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    when(mockProjectSystem.isAndroidProjectViewSupported()).thenReturn(false);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);
    myPane = createPane();

    assertThat(myPane.isInitiallyVisible()).isFalse();
    assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault").isFalse();

    commonAndroidUtilMockedStatic.close();
    projectSystemUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewNotVisibleInNonAndroidProject() {
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    Project project = projectRule.getProject();
    AndroidProjectSystem mockProjectSystem = Mockito.mock(AndroidProjectSystem.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockProjectSystem);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    when(mockProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(false);
    myPane = createPane();

    assertThat(myPane.isInitiallyVisible()).isFalse();
    assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault").isFalse();

    commonAndroidUtilMockedStatic.close();
    projectSystemUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewIsDefaultCustomPropertyHandling() throws Exception {
    Project project = projectRule.getProject();
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    GradleProjectSystem mockGradleProjectSystem = Mockito.mock(GradleProjectSystem.class);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockGradleProjectSystem);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    AndroidNotification myMockNotification = Mockito.mock(AndroidNotification.class);
    new IdeComponents(project).replaceProjectService(AndroidNotification.class, myMockNotification);
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    myPane = createPane();

    when(mockGradleProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);

    try (SystemPropertyOverrides override = new SystemPropertyOverrides()) {
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "true");

      // Assert that setting is set to true, custom property is removed, and user is notified
      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isFalse();
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      assertThat(settings.isProjectViewDefault()).isTrue();
      assertThat(java.lang.Boolean.getBoolean(PROJECT_VIEW_DEFAULT_KEY)).isFalse();

      // Assert that no additional notifications are shown the next time isDefaultPane is called
      myPane.isDefaultPane(project, ideInfo, settings);
      verify(myMockNotification, times(1)).showBalloon("Default Project View Setting Updated",
                                                       "'Set Project view as the default' advanced" +
                                                       " setting was enabled due to the custom property `studio.projectview=true`. We recommend removing this property and using" +
                                                       " 'Advanced Settings -> Project View -> Set Project view as the default` to configure the default project view.",
                                                       NotificationType.INFORMATION);
    }
    projectSystemUtilMockedStatic.close();
    commonAndroidUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewIsDefaultCustomPropertyHandlingWithCustomPropertiesFile() throws Exception {
    Project project = projectRule.getProject();
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    GradleProjectSystem mockGradleProjectSystem = Mockito.mock(GradleProjectSystem.class);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockGradleProjectSystem);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    AndroidNotification myMockNotification = Mockito.mock(AndroidNotification.class);
    new IdeComponents(project).replaceProjectService(AndroidNotification.class, myMockNotification);
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    myPane = createPane();

    MockedStatic<PathManager> mockedPathManager = mockStatic(PathManager.class);
    tempFolder.create();
    File customTempDir = tempFolder.getRoot();
    File ideaFile = tempFolder.newFile(PathManager.PROPERTIES_FILE_NAME);
    java.nio.file.Files.writeString(ideaFile.toPath(), PROJECT_VIEW_DEFAULT_KEY + "=true");
    mockedPathManager.when(PathManager::getCustomOptionsDirectory)
      .thenReturn(customTempDir.getPath());

    when(mockGradleProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);

    try (SystemPropertyOverrides override = new SystemPropertyOverrides()) {
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "true");

      // Assert that setting is set to true, custom property is removed, file is updated, and user is notified
      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isFalse();
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      assertThat(settings.isProjectViewDefault()).isTrue();
      assertThat(java.lang.Boolean.getBoolean(PROJECT_VIEW_DEFAULT_KEY)).isFalse();
      verify(myMockNotification).showBalloon("Default Project View Setting Updated", "'Set Project view as the default' advanced" +
                                                                                     " setting was enabled due to the custom property `studio.projectview=true`. This property has been removed from " +
                                                                                     ideaFile.getPath(), NotificationType.INFORMATION);
      assertThat(java.nio.file.Files.readString(ideaFile.toPath())).doesNotContain(PROJECT_VIEW_DEFAULT_KEY + "=true");
    }
    mockedPathManager.close();
    projectSystemUtilMockedStatic.close();
    commonAndroidUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewIsDefaultCustomPropertyHandlingWithCustomVMPropertiesFile() throws Exception {
    Project project = projectRule.getProject();
    IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
    GradleProjectSystem mockGradleProjectSystem = Mockito.mock(GradleProjectSystem.class);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockGradleProjectSystem);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);
    AndroidNotification myMockNotification = Mockito.mock(AndroidNotification.class);
    new IdeComponents(project).replaceProjectService(AndroidNotification.class, myMockNotification);
    AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
    myPane = createPane();

    tempFolder.create();
    File vmPropertiesFile = tempFolder.newFile("studio64.vmoptions");
    java.nio.file.Files.writeString(vmPropertiesFile.toPath(), "-D" + PROJECT_VIEW_DEFAULT_KEY + "=true");
    when(mockGradleProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);

    try (SystemPropertyOverrides override = new SystemPropertyOverrides()) {
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "true");
      override.setProperty("jb.vmOptionsFile", vmPropertiesFile.getPath());

      // Assert that setting is set to true, custom property is removed, file is updated, and user is notified
      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isFalse();
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      assertThat(settings.isProjectViewDefault()).isTrue();
      assertThat(java.lang.Boolean.getBoolean(PROJECT_VIEW_DEFAULT_KEY)).isFalse();
      verify(myMockNotification).showBalloon("Default Project View Setting Updated", "'Set Project view as the default' advanced" +
                                                                                     " setting was enabled due to the custom property `studio.projectview=true`. This property has been removed from custom VM options.",
                                             NotificationType.INFORMATION);
      assertThat(java.nio.file.Files.readString(vmPropertiesFile.toPath())).doesNotContain(PROJECT_VIEW_DEFAULT_KEY + "=true");
    }

    projectSystemUtilMockedStatic.close();
    commonAndroidUtilMockedStatic.close();
  }

  @Test
  public void testAndroidViewIsDefaultMetrics() throws Exception {
    StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS.override(true);

    try {
      myPane = createPane();
      IdeInfo ideInfo = Mockito.spy(IdeInfo.getInstance());
      GradleProjectSystem mockGradleProjectSystem = Mockito.mock(GradleProjectSystem.class);
    CommonAndroidUtil commonAndroidUtil = Mockito.mock(CommonAndroidUtil.class);
    MockedStatic<CommonAndroidUtil> commonAndroidUtilMockedStatic = Mockito.mockStatic(CommonAndroidUtil.class);
    MockedStatic<ProjectSystemUtil> projectSystemUtilMockedStatic = Mockito.mockStatic(ProjectSystemUtil.class);
    commonAndroidUtilMockedStatic.when(CommonAndroidUtil::getInstance).thenReturn(commonAndroidUtil);AndroidProjectViewSettingsImpl settings = new AndroidProjectViewSettingsImpl();
      Project project = projectRule.getProject();
    projectSystemUtilMockedStatic.when(() -> ProjectSystemUtil.getProjectSystem(project)).thenReturn(mockGradleProjectSystem);
    when(mockGradleProjectSystem.isAndroidProjectViewSupported()).thenReturn(true);
    when(commonAndroidUtil.isAndroidProject(project)).thenReturn(true);

      try (SystemPropertyOverrides override = new SystemPropertyOverrides()) {
      override.setProperty(PROJECT_VIEW_DEFAULT_KEY, "false");
      settings.setDefaultToProjectView(true);

      TestUsageTracker testUsageTracker = new TestUsageTracker(new VirtualTimeScheduler());
      UsageTracker.setWriterForTest(testUsageTracker);

      settings.setDefaultToProjectView(false);
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isTrue();

      settings.setDefaultToProjectView(true);
      assertThat(settings.isDefaultToProjectViewEnabled()).isTrue();
      when(ideInfo.isAndroidStudio()).thenReturn(true);
      when(ideInfo.isGameTools()).thenReturn(false);
      assertThat(myPane.isDefaultPane(project, ideInfo, settings)).named("isDefault(AndroidStudio)").isFalse();

      // Assert that event is not logged when setting is set to current value
      settings.setDefaultToProjectView(true);

      List<AndroidStudioEvent> statsEvents = testUsageTracker.getUsages().stream()
        .map(LoggedUsage::getStudioEvent)
        .filter(event -> event.getKind() == AndroidStudioEvent.EventKind.PROJECT_VIEW_DEFAULT_VIEW_EVENT)
        .toList();
      assertThat(statsEvents.size()).isEqualTo(2);

      AndroidStudioEvent disableDefaultProjectViewEvent = statsEvents.get(0);
      assertThat(disableDefaultProjectViewEvent.getKind()).isEqualTo(AndroidStudioEvent.EventKind.PROJECT_VIEW_DEFAULT_VIEW_EVENT);
      assertThat(disableDefaultProjectViewEvent.getProjectViewDefaultViewEvent().getDefaultView()).isEqualTo(
        ProjectViewDefaultViewEvent.DefaultView.ANDROID_VIEW);

      AndroidStudioEvent enableDefaultProjectViewEvent = statsEvents.get(1);
      assertThat(enableDefaultProjectViewEvent.getKind()).isEqualTo(AndroidStudioEvent.EventKind.PROJECT_VIEW_DEFAULT_VIEW_EVENT);
      assertThat(enableDefaultProjectViewEvent.getProjectViewDefaultViewEvent().getDefaultView()).isEqualTo(
        ProjectViewDefaultViewEvent.DefaultView.PROJECT_VIEW);
    }

      UsageTracker.cleanAfterTesting();
      commonAndroidUtilMockedStatic.close();
      projectSystemUtilMockedStatic.close();
    } finally {
      StudioFlags.SHOW_DEFAULT_PROJECT_VIEW_SETTINGS.clearOverride();
    }
  }

  private static Set<List<String>> getAllNodes(TestAndroidTreeStructure structure) {
    Set<List<String>> result = new HashSet<>();
    Stack<String> path = new Stack<>();
    Object root = structure.getRootElement();
    getAllNodes(structure, root, path, result);
    return result;
  }

  private static void getAllNodes(TestAndroidTreeStructure structure,
                                  Object node,
                                  Stack<String> path,
                                  Set<List<String>> result) {
    for (Object child : structure.getChildElements(node)) {
      String nodeName = ((AbstractTreeNode)child).toTestString(null);
      if (structure.getChildElements(child).length == 0) {
        ArrayList<String> newPath = new ArrayList<>(path);
        newPath.add(nodeName);
        result.add(newPath);
      }
      else {
        path.push(nodeName);
        getAllNodes(structure, child, path, result);
        path.pop();
      }
    }
  }

  private class TestAndroidTreeStructure extends TestProjectTreeStructure {
    private TestAndroidTreeStructure(Project project, Disposable parentDisposable) {
      super(project, parentDisposable);
    }

    @Override
    public List<TreeStructureProvider> getProviders() {
      List<TreeStructureProvider> providers = super.getProviders();
      if (providers == null) {
        return null;
      }
      return ContainerUtil.map(providers, provider -> new BuildScriptTreeStructureProvider(provider));
    }

    @Override
    protected AbstractTreeNode<?> createRoot(@NotNull Project project, @NotNull ViewSettings settings) {
      return new AndroidViewProjectNode(project, settings);
    }

    @Override
    public boolean isHideEmptyMiddlePackages() {
      return true;
    }
  }

  @NotNull
  private AndroidProjectViewPane createPane() {
    AndroidProjectViewPane pane = new AndroidProjectViewPane(projectRule.getProject());
    pane.createComponent();
    Disposer.register(projectRule.getProject(), pane);
    return pane;
  }
}